﻿#include "OyunAPI.h"
//#include "../bcrypt/include/bcrypt.h"
#include <iostream>
#include <fstream>
#include <locale.h>
//#include <curl/curl.h>

#define CRY_POCO

#ifndef CRY_POCO

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

#include "../cryptopp/cryptlib.h"
#include "../cryptopp/md5.h"
#include "../cryptopp/hex.h"

#else // !CRY_POCO

#include "../poco/Foundation/include/Poco/MD5Engine.h"
#include "../poco/Foundation/include/Poco/DigestStream.h"
#include "../poco/Foundation/include/Poco/StreamCopier.h"

#endif // CRY_POCO


#define _(x) gettext(x)

httplib::Client OyunAPI::OyunClient("http://oy.nmgoyun.com");
OyunAPI::Account OyunAPI::OyunAccount;

namespace ErrorMessage
{
    const char* Empty = "没有选中任何内容";
    const char* TooLong = "长度超出限制，最多1000个字符";
    const char* Http = "请求失败，Http错误码：";
    const char* Oyun = "请求失败，错误码：";
    const char* ConnectFail = "无法链接到服务器，请重试";
}

unsigned char ToHex(unsigned char x)
{
    return  x > 9 ? x + 55 : x + 48;
}

unsigned char FromHex(unsigned char x)
{
    unsigned char y;
    if (x >= 'A' && x <= 'Z') y = x - 'A' + 10;
    else if (x >= 'a' && x <= 'z') y = x - 'a' + 10;
    else if (x >= '0' && x <= '9') y = x - '0';
    else assert(0);
    return y;
}

std::string UrlEncode(const std::string& str)
{
    std::string strTemp = "";
    size_t length = str.length();
    for (size_t i = 0; i < length; i++)
    {
        if (isalnum((unsigned char)str[i]) ||
            (str[i] == '-') ||
            (str[i] == '_') ||
            (str[i] == '.') ||
            (str[i] == '~'))
            strTemp += str[i];
        else if (str[i] == ' ')
            strTemp += "+";
        else
        {
            strTemp += '%';
            strTemp += ToHex((unsigned char)str[i] >> 4);
            strTemp += ToHex((unsigned char)str[i] % 16);
        }
    }
    return strTemp;
}

std::string UrlDecode(const std::string& str)
{
    std::string strTemp = "";
    size_t length = str.length();
    for (size_t i = 0; i < length; i++)
    {
        if (str[i] == '+') strTemp += ' ';
        else if (str[i] == '%')
        {
            assert(i + 2 < length);
            unsigned char high = FromHex((unsigned char)str[++i]);
            unsigned char low = FromHex((unsigned char)str[++i]);
            strTemp += high * 16 + low;
        }
        else strTemp += str[i];
    }
    return strTemp;
}

OyunAPIException::OyunAPIException(const char* message) noexcept
    : Message(message)
{ }

OyunAPIException::OyunAPIException(const std::string& message) noexcept
    : Message(message)
{ }

void OyunAPI::InitLanguage(const char* locale)
{
    setlocale(LC_ALL, locale);
    ErrorMessage::ConnectFail = _(ErrorMessage::ConnectFail);
    ErrorMessage::Empty = _(ErrorMessage::Empty);
    ErrorMessage::Http = _(ErrorMessage::Http);
    ErrorMessage::Oyun = _(ErrorMessage::Oyun);
    ErrorMessage::TooLong = _(ErrorMessage::TooLong);
}

void OyunAPI::InitOyunAccount(const char* keyFilePath)
{
    std::ifstream keyFileIn(keyFilePath);
    std::string key;
    keyFileIn >> key;
}

void OyunAPI::InitOyunAccount(const OyunAPI::Account& account)
{
    OyunAccount = account;
}

inline void OyunAPI::FixEndline(std::string& text)
{
    std::regex_replace(text, std::regex("\\\\n"), "\n");
}

inline std::string OyunAPI::GenerateSign(const std::vector<std::pair<const char*, const char*>> values)
{
    std::string signSource;

    for (const auto& value : values)
    {
        signSource += value.first;
        signSource += '=';
        signSource += value.second;
        signSource += '&';
    }

    signSource.pop_back();

    return GenerateMD5(signSource);
}

inline std::string OyunAPI::GenerateMD5(const std::string& text)
{
#ifndef CRY_POCO

    CryptoPP::Weak1::MD5 md5;
    CryptoPP::byte digest[CryptoPP::Weak1::MD5::DIGESTSIZE];

    md5.CalculateDigest(digest, (CryptoPP::byte*)text.c_str(), text.length());

    // 将md5 digest转换成hex string 格式
    std::string result;
    CryptoPP::HexEncoder encoder;
    encoder.Attach(new CryptoPP::StringSink(result));
    encoder.Put(digest, sizeof(digest));
    encoder.MessageEnd();

    return result;

#else

    Poco::MD5Engine md5;
    Poco::DigestOutputStream dos(md5);

    std::istringstream istr(text);

    Poco::StreamCopier::copyStream(istr, dos);
    dos.close();

    return Poco::DigestEngine::digestToHex(md5.digest());

#endif
}

inline Json::Value OyunAPI::GetResponseJson(const httplib::Result& response)
{
    if (response == nullptr)
        throw OyunAPIException(ErrorMessage::ConnectFail);

    if (response->status != 200)
        throw OyunAPIException(ErrorMessage::Http + std::to_string(response->status));

    Json::Reader reader;
    Json::Value result;

    reader.parse(response->body, result);

    if (result["code"] != "0000")
        throw OyunAPIException(ErrorMessage::Oyun + result["code"].asString());

    return result;
}

//#define CURL_NEW_API
#define HTTPLIB_NEW_API

std::string OyunAPI::Translate(const char* text)
{
#ifdef CURL_NEW_API
    CURL* curl = nullptr;
    CURLcode result;
    std::string responseBody;

    curl = curl_easy_init();

    curl_slist* headerList = nullptr;
    headerList = curl_slist_append(headerList, "Content-Type:multipart/form-data");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
    curl_easy_setopt(curl, CURLOPT_URL, "http://fy.mglip.com/serviceT2tSimple");
    curl_easy_setopt(curl, CURLOPT_POST, 1);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, std::string("from=mon&to=zh&userInput=") + text);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, responseBody);
    result = curl_easy_perform(curl);

    printf("[%s] %s\n", __func__, responseBody);

    return "";
#elif defined(HTTPLIB_NEW_API)

    httplib::MultipartFormDataItems formData;

    formData.emplace_back(httplib::MultipartFormData({ "userInput", text, "", "" }));
    formData.emplace_back(httplib::MultipartFormData({ "from", "mon", "", ""}));
    formData.emplace_back(httplib::MultipartFormData({ "to", "zh", "", "" }));

    httplib::Client client("http://fy.mglip.com");

    clock_t startTime = clock();

    httplib::Result response = client.Post("/serviceT2tSimple", formData);

    if (response == nullptr)
        throw OyunAPIException(ErrorMessage::ConnectFail);

    if (response->status != 200)
        throw OyunAPIException(ErrorMessage::Http + std::to_string(response->status));

    Json::Reader reader;
    Json::Value resultJson;

    reader.parse(response->body, resultJson);

    return resultJson["datas"]["tgt"].asString();

#else
    int length = strlen(text);

    if (length <= 0)
        throw OyunAPIException(ErrorMessage::Empty);

    if (length <= 0 || length > 1000)
        throw OyunAPIException(ErrorMessage::TooLong);

    const char* nonce = "76d753c59b0f4704";
    const std::string timeStampStr = std::to_string(time(nullptr)).c_str();
    std::string sign =
        GenerateSign
        ({
            { "inputStr", text },
            { "nonce", nonce },
            { "pid", OyunAccount.PID },
            { "timestamp", timeStampStr.c_str() },
            { "type", "1" }
            });

    Json::Value json;
    json["pid"] = OyunAccount.PID;
    json["sign"] = sign;
    json["timestamp"] = timeStampStr;
    json["nonce"] = nonce;
    json["type"] = "1";
    json["inputStr"] = text;

    clock_t startTime = clock();

    httplib::Result response = OyunClient.Post("/api/fy/v1", json.toStyledString(), "application/json");

    Json::Value resultJson = GetResponseJson(response);

    std::string result = resultJson["data"].asString();

    FixEndline(result);

    return result;

#endif
}

std::string OyunAPI::TextToSpeech(const char* text)
{
    //int length = strlen(text);

    //if (length <= 0)
    //    throw OyunAPIException(ErrorMessage::Empty);

    //if (length <= 0 || length > 1000)
    //    throw OyunAPIException(ErrorMessage::TooLong);

    std::string sText = "http://202.207.12.163:22337/synthesize?text=" + UrlEncode(text);

    //std::ofstream fout("output.txt");
    //fout << sText.length() << std::endl;

    return sText;

    /*int length = strlen(text);

    if (length <= 0)
        throw OyunAPIException(ErrorMessage::Empty);

    if (length <= 0 || length > 1000)
        throw OyunAPIException(ErrorMessage::TooLong);

    const char* nonce = "76d753c59b0f470496a00c4e17434065";
    const std::string timeStampStr = std::to_string(time(nullptr));
    const std::string sign =
        GenerateSign
        ({
            { "inputStr", text },
            { "nonce", nonce },
            { "pid", OyunAccount.PID },
            { "timestamp", timeStampStr.c_str() }
            });

    Json::Value json;
    json["pid"] = OyunAccount.PID;
    json["sign"] = sign;
    json["timestamp"] = timeStampStr.c_str();
    json["nonce"] = nonce;
    json["inputStr"] = text;

    httplib::Result response = OyunClient.Post("/api/tts/v2", json.toStyledString(), "application/json");

    Json::Value resultJson = GetResponseJson(response);

    std::string result = resultJson["data"].asString();

    return result;*/
}

std::string OyunAPI::Correction(const char* text)
{
    const char* nonce = "76d753c59b0f470496a00c4e17434065";
    const std::string timeStampStr = std::to_string(time(nullptr));
    const std::string sign =
        GenerateSign
        ({
            { "inputVersion", "1" },
            { "inputStr", text },
            { "nonce", nonce },
            { "pid", OyunAccount.PID },
            { "outVersion", "1" },
            { "timestamp", timeStampStr.c_str() }
        });

    Json::Value json;
    json["pid"] = OyunAccount.PID;
    json["sign"] = sign;
    json["timestamp"] = timeStampStr.c_str();
    json["nonce"] = nonce;
    json["inputStr"] = text;
    json["inputVersion"] = 1;
    json["outVersion"] = 1;

    httplib::Result response = OyunClient.Post("/api/check/v3", json.toStyledString(), "application/json");

    Json::Value resultJson = GetResponseJson(response);

    std::string result = resultJson["data"]["correctionMongolian"].asString();

    return result;
}

std::vector<std::pair<std::string, std::string>> OyunAPI::Dictionary(const char* text)
{
    const char* nonce = "76d753c59b0f470496a00c4e17434065";
    const std::string timeStampStr = std::to_string(time(nullptr));
    const std::string sign =
        GenerateSign
        ({
            { "inputStr", text },
            { "nonce", nonce },
            { "pid", OyunAccount.PID },
            { "timestamp", timeStampStr.c_str() },
            { "type", "2" }
            });

    Json::Value json;
    json["pid"] = OyunAccount.PID;
    json["sign"] = sign;
    json["timestamp"] = timeStampStr;
    json["nonce"] = nonce;
    json["type"] = 2;
    json["inputStr"] = text;

    httplib::Result response = OyunClient.Post("/api/dictionary/v1", json.toStyledString(), "application/json");

    Json::Value resultJson = GetResponseJson(response);

    std::vector<std::pair<std::string, std::string>> result;

    result.emplace_back(resultJson["data"]["input"].asString(), resultJson["data"]["toInput"].asString());

    Json::Value relatedList = resultJson["data"]["relatedList"];

    for (Json::ArrayIndex i = 0; i < relatedList.size(); i++)
    {
        result.emplace_back(relatedList[i]["name"].asString(), relatedList[i]["toName"].asString());
    }

    return result;
}

std::string OyunAPI::Transcoding(const char* text)
{

}